/**
 * Title :	jFamilyTree generator
 * Description: jFamilyTree generator
 * Version:	provider
 * Copyright (c) 2001-2008
 * Terms of use:see license agreement at http://treebolic.sourceforge.net/en/license.htm
 * Author: Bernard Bou
 * Company: bsys
 */
package jFamilyTree.generator;

import java.awt.Component;
import java.awt.Image;
import java.awt.Insets;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.text.JTextComponent;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;

import org.w3c.dom.Document;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import jFamilyTree.IContext;
import jFamilyTree.IWidget;
import jFamilyTree.Messages;
import jFamilyTree.commons.ButtonTabComponent;
import jFamilyTree.commons.FileDialogs;
import jFamilyTree.commons.Help;
import jFamilyTree.commons.OpenDialog;
import jFamilyTree.commons.Persist;
import jFamilyTree.commons.XslExportDialog;
import jFamilyTree.commons.XslImportDialog;
import jFamilyTree.dialogs.main.XSettingsDialog;
import jFamilyTree.dialogs.main.XSiteDialog;
import jFamilyTree.dialogs.main.XUrlDialog;
import jFamilyTree.dtd.Dtd;
import jFamilyTree.generator.domtree.DomTreeView;
import jFamilyTree.generator.model.ModelUtils;
import jFamilyTree.generator.tree.TreeAdapter;
import jFamilyTree.generator.tree.TreeView;
import jFamilyTree.model.IProvider;
import jFamilyTree.model.IProviderContext;
import jFamilyTree.model.Model;
import jFamilyTree.model.MutableEdge;
import jFamilyTree.model.MutableNode;
import jFamilyTree.model.Settings;
import jFamilyTree.model.Tree;
import jFamilyTree.model.TreeMutableEdge;
import jFamilyTree.model.TreeMutableNode;
import jFamilyTree.provider.xml.dom.DocumentAdapter;
import jFamilyTree.provider.xml.dom.Parser;
import jFamilyTree.site.MakeSiteDialog;
import jFamilyTree.site.RunSiteDialog;
import bsys.propertyview.PickListener;
import bsys.xml.transformer.DomTransformer;

/**
 * Controller
 * 
 * @author Bernard Bou
 */
public class Controller implements IContext, IProviderContext, PickListener, ChangeListener, CellEditorListener, HyperlinkListener
{
	// T R E E B O L I C . W I D G E T

	/**
	 * jFamilyTree widget
	 */
	private IWidget theWidget;

	/**
	 * jFamilyTree model
	 */
	public Model theModel;

	/**
	 * Id to node map
	 */
	private Map<String, MutableNode> theIdToNodeMap;

	// D O C U M E N T

	/**
	 * Document
	 */
	private Document theDocument;

	/**
	 * Document url
	 */
	private URL theUrl;

	/**
	 * Document mode
	 */
	private Mode theMode;

	/**
	 * XML validate
	 */
	static public boolean validate = true;

	// T R E E

	/**
	 * Tree model
	 */
	public TreeModel theTreeModel;

	/**
	 * Tree view
	 */
	private TreeView theTreeView;

	// P R O P E R T Y . V I E W

	private PropertyView thePropertyView;

	// O T H E R . V I E W S

	/**
	 * Text view
	 */
	private TextView theTextView;

	/**
	 * Dom tree view
	 */
	private DomTreeView theDomTreeView;

	// F R A M E

	/**
	 * Frame
	 */
	private JFrame theFrame;

	// T A B B E D P A N E

	/**
	 * Tabbed pane
	 */
	private JTabbedPane theTabbedPane;

	/**
	 * Tabbed pane selection index
	 */
	private int theSelectedTabIndex;

	// C O M M A N D L I N E

	/**
	 * Command line arguments
	 */
	private String[] theArgs;

	// C O M M A N D . C O D E S

	/**
	 * Command code
	 */
	static public enum Code
	{
		NEW, OPEN, OPENURL, IMPORTXSL, IMPORTPROVIDER, //
		EXPORT, SAVE, SAVEAS, NEWELEMENT, REMOVEELEMENT, //
		EXPORTSETTINGS, //
		NORMALIZEIDS, LISTIMAGES, LISTLINKS, LISTMOUNTS, LISTIDS, //
		MAKESITE, RUNSITE, //
		SETTINGS, SETTINGSBASE, SETTINGSURL, //
		UPDATE, //
		OPTIONTREEBOLICRENDERER, OPTIONVALIDATEXML, OPTIONFOCUSPARENT, //
		EXPANDTREE, COLLAPSETREE, //
		SELECTTOP, SELECTTREE, SELECTNODES, SELECTEDGES, //
		DTD, //
		ABOUT, HELP
	};

	// O P E N . M O D E S

	/**
	 * Command code
	 */
	static public enum Mode
	{
		NEW, OPEN, IMPORT
	};

	// U P D A T E . R O U T I N E S

	/**
	 * Map component to update action
	 */
	public Map<Component, Runnable> theUpdateMap;

	// C O N S T R U C T O R

	/**
	 * Constructor
	 * 
	 * @param theseArgs
	 *        command line arguments
	 */
	public Controller(String[] theseArgs)
	{
		theArgs = theseArgs;
		theDocument = null;
		theUrl = null;
		theMode = null;
		theSettings = Persist.getSettings("jFamilyTree-generator"); //$NON-NLS-1$
		theUpdateMap = new HashMap<Component, Runnable>();
	}

	// C O N N E C T

	/**
	 * Connect to frame
	 * 
	 * @param thisFrame
	 *        frame
	 */
	public void connect(JFrame thisFrame)
	{
		theFrame = thisFrame;
	}

	/**
	 * Connect to tree view
	 * 
	 * @param thisTreeView
	 *        treeview
	 */
	public void connect(TreeView thisTreeView)
	{
		theTreeView = thisTreeView;
	}

	/**
	 * Connect to property view
	 * 
	 * @param thisPropertyView
	 *        property view
	 */
	public void connect(PropertyView thisPropertyView)
	{
		thePropertyView = thisPropertyView;
	}

	/**
	 * Connect to text view
	 * 
	 * @param thisTextView
	 *        text view
	 */
	public void connect(TextView thisTextView)
	{
		theTextView = thisTextView;
	}

	/**
	 * Connect to Dom tree view
	 * 
	 * @param thisDomTreeView
	 *        DOM tree view
	 */
	public void connect(DomTreeView thisDomTreeView)
	{
		theDomTreeView = thisDomTreeView;
	}

	/**
	 * Connect to widget
	 * 
	 * @param thisWidget
	 *        widget
	 */
	public void connect(IWidget thisWidget)
	{
		theWidget = thisWidget;
	}

	/**
	 * Connect to tabbed pane
	 * 
	 * @param thisTabbedPane
	 *        tabbed pane
	 */
	public void connect(JTabbedPane thisTabbedPane)
	{
		theTabbedPane = thisTabbedPane;
		theSelectedTabIndex = theTabbedPane.getSelectedIndex();
	}

	// D O C U M E N T

	/**
	 * Make document
	 * 
	 * @return document
	 */
	private Document makeDocument(Model thisModel)
	{
		return ModelToDocumentTransformer.transform(thisModel);
	}

	/**
	 * Set document
	 * 
	 * @param thisDocument
	 *        document
	 * @param thisURL
	 *        url
	 */
	private void setDocument(Document thisDocument, URL thisURL)
	{
		theDocument = thisDocument;
		theUrl = theDocument == null ? null : thisURL;
		theModel = null;
		theIdToNodeMap = null;

		if (theDocument == null)
			return;

		// document -> model
		DocumentAdapter thisAdapter = new DocumentAdapter()
		{
			@Override
			protected MutableNode makeNode(MutableNode thisParent, String thisId)
			{
				return new TreeMutableNode(thisParent, thisId);
			}

			@Override
			protected MutableEdge makeEdge(MutableNode thisFromNode, MutableNode thisToNode)
			{
				return new TreeMutableEdge(thisFromNode, thisToNode);
			}
		};
		theModel = thisAdapter.makeModel(theDocument);
		theIdToNodeMap = thisAdapter.getIdToNodeMap();
	}

	// M O D E L

	/**
	 * Get model
	 * 
	 * @return model
	 */
	public Model getModel()
	{
		return theModel;
	}

	/**
	 * Set model
	 * 
	 * @param thisModel
	 *        model
	 * @param thisIdToNodeMap
	 *        id to node map
	 */
	private void setModel(Model thisModel, Map<String, MutableNode> thisIdToNodeMap)
	{
		theDocument = null;
		theUrl = null;
		theModel = thisModel;
		theIdToNodeMap = thisIdToNodeMap;
	}

	// S E T T I N G S

	/**
	 * Settings
	 */
	private Properties theSettings;

	// L I S T E N E R S

	// from tree

	/*
	 * (non-Javadoc)
	 * @see bsys.propertyview.PickListener#picked(java.lang.Object)
	 */
	public void picked(final Object thisObject)
	{
		// relay to property view
		thePropertyView.picked(thisObject);
		// thePropertyView.requestFocus();
	}

	// from tab

	/*
	 * (non-Javadoc)
	 * @see javax.swing.event.ChangeListener#stateChanged(javax.swing.event.ChangeEvent)
	 */
	public void stateChanged(ChangeEvent thisEvent)
	{
		// on leaving tab 0
		if (theSelectedTabIndex == 0)
			theDocument = makeDocument(theModel);

		theSelectedTabIndex = theTabbedPane.getSelectedIndex();
		updateView();
	}

	// from property view

	/*
	 * (non-Javadoc)
	 * @see javax.swing.event.CellEditorListener#editingStopped(javax.swing.event.ChangeEvent)
	 */
	public void editingStopped(ChangeEvent thisEvent)
	{
		theTreeView.repaint();
	}

	/*
	 * (non-Javadoc)
	 * @see javax.swing.event.CellEditorListener#editingCanceled(javax.swing.event.ChangeEvent)
	 */
	public void editingCanceled(ChangeEvent thisEvent)
	{
	}

	/*
	 * (non-Javadoc)
	 * @see javax.swing.event.HyperlinkListener#hyperlinkUpdate(javax.swing.event.HyperlinkEvent)
	 */
	public void hyperlinkUpdate(HyperlinkEvent thisEvent)
	{
		if (thisEvent.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
		{
			JEditorPane thisView = (JEditorPane) thisEvent.getSource();
			URL thisURL = thisEvent.getURL();
			try
			{
				thisView.setPage(thisURL);
			}
			catch (IOException e)
			{
				thisView.setText(Messages.getString("Controller.220") + thisEvent.getURL()); //$NON-NLS-1$
			}
		}
	}

	// E X E C U T E

	/*
	 * (non-Javadoc)
	 * @see jFamilyTree.generator.IController#execute(int, int)
	 */
	public void execute(Code thisCode, int thisValue)
	{
		switch (thisCode)
		{
		case NEW:
			newDocument();
			break;

		case OPEN:
			open();
			break;

		case OPENURL:
			openUrl();
			break;

		case IMPORTXSL:
			importXsl();
			break;

		case IMPORTPROVIDER:
			importProvider();
			break;

		case SAVE:
			save();
			break;

		case SAVEAS:
			saveAs();
			break;

		case EXPORT:
			exportDocument();
			break;

		case EXPORTSETTINGS:
			exportSettings();
			break;

		case NEWELEMENT:
			newElement();
			break;

		case REMOVEELEMENT:
			removeElement();
			break;

		case NORMALIZEIDS:
			normalizeIds();
			break;

		case LISTIMAGES:
			listImages();
			break;

		case LISTLINKS:
			listLinks();
			break;

		case LISTMOUNTS:
			listMounts();
			break;

		case LISTIDS:
			listIds();
			break;

		case UPDATE:
			updateWidget();
			break;

		case MAKESITE:
			makeSite();
			break;

		case RUNSITE:
			runSite();
			break;

		case SETTINGS:
			settings();
			break;

		case SETTINGSBASE:
			base();
			break;

		case SETTINGSURL:
			url();
			break;

		case OPTIONTREEBOLICRENDERER:
			optionjFamilyTreeRenderer();
			break;

		case OPTIONVALIDATEXML:
			optionValidateXml();
			break;

		case OPTIONFOCUSPARENT:
			optionFocusParent();
			break;

		case EXPANDTREE:
			theTreeView.expand();
			break;

		case COLLAPSETREE:
			theTreeView.collapse();
			break;

		case SELECTTOP:
			select("TopWrapper"); //$NON-NLS-1$
			break;

		case SELECTTREE:
			select("TreeWrapper"); //$NON-NLS-1$
			break;

		case SELECTNODES:
			select("NodesWrapper"); //$NON-NLS-1$
			break;

		case SELECTEDGES:
			select("EdgesWrapper"); //$NON-NLS-1$
			break;

		case ABOUT:
			about();
			break;

		case DTD:
			dtd();
			break;

		case HELP:
			help();
			break;

		default:
			System.err.println("Unhandled action: " + thisCode); //$NON-NLS-1$
			break;
		}
	}

	// C O M M A N D S

	/**
	 * Exit hook
	 */
	public void exit()
	{
		checkSave();
		Persist.saveSettings("jFamilyTree-generator", theSettings); //$NON-NLS-1$
	}

	/**
	 * New command
	 */
	private void newDocument()
	{
		checkSave();
		Model thisModel = new Model(new Tree(TreeMutableNode.makeDefaultTree(), null), new Settings());
		setModel(thisModel, ModelUtils.makeIdToNodeMap(thisModel));
		update(Mode.NEW);
	}

	/**
	 * Open
	 */
	private void open()
	{
		checkSave();
		open(FileDialogs.getXmlUrl(theSettings.getProperty("base", "."))); //$NON-NLS-1$ //$NON-NLS-2$
	}

	/**
	 * Open Http
	 */
	private void openUrl()
	{
		checkSave();
		XUrlDialog thisDialog = new XUrlDialog(theSettings);
		thisDialog.setModal(true);
		thisDialog.setVisible(true);
		thisDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
		if (thisDialog.ok)
			open(theSettings.getProperty("openurl")); //$NON-NLS-1$
	}

	/**
	 * Open Url string
	 * 
	 * @param thisUrlString
	 *        Url string
	 */
	public void open(String thisUrlString)
	{
		URL thisUrl = makeURL(thisUrlString);
		if (thisUrl == null)
			return;
		open(thisUrl);
	}

	/**
	 * Open Url
	 * 
	 * @param thisUrl
	 *        Url
	 */
	private void open(URL thisUrl)
	{
		setDocument(new Parser(validate).makeDocument(thisUrl, makeEntityResolver()), thisUrl);
		update(Mode.OPEN);
	}

	/**
	 * Make entity resolver (for validation)
	 * 
	 * @return jFamilyTree entity resolver
	 */
	protected EntityResolver makeEntityResolver()
	{
		return new EntityResolver()
		{
			public InputSource resolveEntity(String thisPublicId, String thisSystemId) throws SAXException, IOException
			{
				URL thisUrl = Dtd.class.getResource("jFamilyTree.dtd"); //$NON-NLS-1$
				return new InputSource(thisUrl.openStream());
			}
		};
	}

	/**
	 * Get file from Url
	 * 
	 * @param thisUrl
	 *        Url
	 * @return file
	 */
	static private File getFile(URL thisUrl)
	{
		try
		{
			return new File(thisUrl.toURI());
		}
		catch (URISyntaxException thisException)
		{
		}
		return null;
	}

	/**
	 * Get file from Url
	 * 
	 * @param thisUrlString
	 *        url string
	 * @return file
	 */
	static private File getFile(String thisUrlString)
	{
		try
		{
			return getFile(new URL(thisUrlString));
		}
		catch (MalformedURLException thisException)
		{
		}
		return null;
	}

	/**
	 * Save
	 */
	private void save()
	{
		if (theMode != Mode.OPEN || theUrl == null)
		{
			saveAs();
			return;
		}
		File thisFile = getFile(theUrl);
		save(thisFile);
	}

	/**
	 * Save as
	 */
	private void saveAs()
	{
		String thisFilePath = FileDialogs.getXml(theSettings.getProperty("base", ".")); //$NON-NLS-1$ //$NON-NLS-2$
		if (thisFilePath == null)
			return;
		File thisFile = new File(thisFilePath);
		if (thisFile.exists() && !confirm(new String[] { thisFilePath, Messages.getString("Controller.40"), Messages.getString("Controller.41") })) //$NON-NLS-1$ //$NON-NLS-2$
			return;
		save(thisFile);
	}

	/**
	 * Save as file
	 * 
	 * @param thisFile
	 *        file
	 */
	private void save(File thisFile)
	{
		if (thisFile != null)
		{
			try
			{
				theDocument = makeDocument(theModel);
				if (theDocument != null)
				{
					new DomTransformer(false, "jFamilyTree.dtd").documentToFile(theDocument, thisFile); //$NON-NLS-1$
					theTreeView.dirty = false;
					thePropertyView.dirty = false;
				}
			}
			catch (TransformerConfigurationException thisException)
			{
				System.err.println(Messages.getString("Controller.30") + thisFile + " " + thisException.toString()); //$NON-NLS-1$ //$NON-NLS-2$
			}
			catch (TransformerException thisException)
			{
				System.err.println(Messages.getString("Controller.30") + thisFile + " " + thisException.toString()); //$NON-NLS-1$ //$NON-NLS-2$
			}
			catch (IOException thisException)
			{
				System.err.println(Messages.getString("Controller.30") + thisFile + " " + thisException.toString()); //$NON-NLS-1$ //$NON-NLS-2$
			}
		}
	}

	/**
	 * Check if save is needed
	 */
	private void checkSave()
	{
		if (theTreeView.dirty || thePropertyView.dirty)
			if (confirm(new String[] { theUrl == null ? Messages.getString("Controller.60") : theUrl.toString(), Messages.getString("Controller.31"), Messages.getString("Controller.32") })) //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			{
				if (theMode != Mode.OPEN || theUrl == null)
					saveAs();
				else
					save();
			}
	}

	/**
	 * Import document through Xsl
	 */
	private void importXsl()
	{
		checkSave();
		XslImportDialog thisDialog = new XslImportDialog(theSettings);
		thisDialog.setModal(true);
		thisDialog.setVisible(true);
		thisDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
		if (thisDialog.ok)
		{
			String thisXml = theSettings.getProperty("importurl"); //$NON-NLS-1$
			String thisXslt = theSettings.getProperty("importxsl"); //$NON-NLS-1$
			if (thisXml != null && thisXslt != null)
			{
				theUrl = null;
				try
				{
					URL thisXmlUrl = new URL(thisXml);
					URL thisXsltUrl = new URL(thisXslt);
					setDocument(new Parser().makeDocument(thisXmlUrl, thisXsltUrl, null), thisXmlUrl);
					update(Mode.IMPORT);
				}
				catch (MalformedURLException thisException)
				{
					thisException.printStackTrace();
				}
			}
		}
	}

	/**
	 * Import document from provider
	 */
	private void importProvider()
	{
		checkSave();
		OpenDialog thisDialog = new OpenDialog(theSettings);
		thisDialog.setModal(true);
		thisDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
		thisDialog.setVisible(true);
		if (thisDialog.ok)
		{
			String thisSource = theSettings.getProperty("source"); //$NON-NLS-1$
			String thisProviderName = theSettings.getProperty("provider"); //$NON-NLS-1$
			if (thisProviderName != null && !thisProviderName.isEmpty() && thisSource != null && !thisSource.isEmpty())
			{
				// make provider
				IProvider thisProvider = makeProvider(thisProviderName);
				if (thisProvider == null)
				{
					String[] theseLines = { Messages.getString("Controller.4"), thisProviderName, Messages.getString("Controller.250") }; //$NON-NLS-1$ //$NON-NLS-2$
					JOptionPane.showMessageDialog(null, theseLines, Messages.getString("Controller.3"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$
					return;
				}
				thisProvider.connect(this);

				// make model
				Model thisModel = thisProvider.makeModel(thisSource);
				if (thisModel == null)
				{
					String[] theseLines = { Messages.getString("Controller.2"), thisProviderName, Messages.getString("Controller.242"), thisSource }; //$NON-NLS-1$ //$NON-NLS-2$
					JOptionPane.showMessageDialog(null, theseLines, Messages.getString("Controller.3"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$
					return;
				}

				// make model mutable
				Pair<Model, Map<String, MutableNode>> thisResult = ModelUtils.toMutable(thisModel);
				if (thisResult.theFirst == null)
				{
					String[] theseLines = { Messages.getString("Controller.2"), thisProviderName, Messages.getString("Controller.241"), thisSource }; //$NON-NLS-1$ //$NON-NLS-2$
					JOptionPane.showMessageDialog(null, theseLines, Messages.getString("Controller.3"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$
					return;
				}

				setModel(thisResult.theFirst, thisResult.theSecond);
				update(Mode.IMPORT);
			}
		}
	}

	/**
	 * Export document
	 */
	private void exportDocument()
	{
		XslExportDialog thisDialog = new XslExportDialog(theSettings);
		thisDialog.setModal(true);
		thisDialog.setVisible(true);
		thisDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
		if (thisDialog.ok)
		{
			String thisExportUrl = theSettings.getProperty("exporturl"); //$NON-NLS-1$
			String thisXsltUrl = theSettings.getProperty("exportxsl"); //$NON-NLS-1$
			if (thisExportUrl.startsWith("view:")) //$NON-NLS-1$
				transformToView(thisExportUrl, thisXsltUrl);
			else
			{
				transformToFile(thisExportUrl, thisXsltUrl, thisExportUrl);
			}
		}
	}

	/**
	 * Export settings
	 */
	private void exportSettings()
	{
		Settings theseSettings = theModel.theSettings;
		ModelUtils.saveSettings(theseSettings, FileDialogs.getPropertyFile(theSettings.getProperty("base", "."))); //$NON-NLS-1$ //$NON-NLS-2$
	}

	// E D I T I N G

	/**
	 * New element
	 */
	private void newElement()
	{
		theTreeView.editAdd();
	}

	/**
	 * Remove element
	 */
	private void removeElement()
	{
		theTreeView.editRemove();
	}

	/**
	 * Normalize ids
	 */
	private void normalizeIds()
	{
		String thisPrefix = ask(Messages.getString("Controller.10"), "id"); //$NON-NLS-1$ //$NON-NLS-2$
		if (thisPrefix == null)
			return;
		theIdToNodeMap = ModelUtils.normalizeIds(theModel, thisPrefix);
		theDocument = makeDocument(theModel);
		updateView();
		theTreeView.dirty = true;
		thePropertyView.dirty = true;
	}

	// L I S T S

	/**
	 * List images
	 */
	private void listImages()
	{
		if (theModel == null)
			return;
		ImageListDialog thisDialog = new ImageListDialog(this);
		thisDialog.setVisible(true);
		thisDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
	}

	/**
	 * List links
	 */
	private void listLinks()
	{
		if (theModel == null)
			return;
		LinkListDialog thisDialog = new LinkListDialog(this);
		thisDialog.setVisible(true);
		thisDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
	}

	/**
	 * List mounts
	 */
	private void listMounts()
	{
		if (theModel == null)
			return;
		MountListDialog thisDialog = new MountListDialog(this);
		thisDialog.setVisible(true);
		thisDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
	}

	/**
	 * List mounts
	 */
	private void listIds()
	{
		if (theModel == null)
			return;
		IdListDialog thisDialog = new IdListDialog(this);
		thisDialog.setVisible(true);
		thisDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
	}

	// D T D

	/**
	 * Get Dtd
	 */
	public void dtd()
	{
		TextView thisView = makeTextView();
		JComponent thisComponent = new JScrollPane(thisView);
		theTabbedPane.addTab("DTD", null, thisComponent, null); //$NON-NLS-1$
		int thisIndex = theTabbedPane.indexOfComponent(thisComponent);
		theTabbedPane.setTabComponentAt(thisIndex, new ButtonTabComponent(theTabbedPane));
		theTabbedPane.setSelectedIndex(thisIndex);

		String thisText = Dtd.getString();
		thisView.setText(thisText);
		thisView.setCaretPosition(0);
	}

	// B E H A V I O U R

	/**
	 * Toggle jFamilyTree rendering in Dom tree view
	 */
	private void optionjFamilyTreeRenderer()
	{
		TreeCellRenderer thatRenderer = theDomTreeView.getCellRenderer();
		boolean isSpecific = thatRenderer instanceof jFamilyTree.generator.domtree.treebolic.Renderer;
		TreeCellRenderer thisRenderer = isSpecific ? new jFamilyTree.generator.domtree.Renderer() : new jFamilyTree.generator.domtree.treebolic.Renderer();
		theDomTreeView.setCellRenderer(thisRenderer);
		theDomTreeView.repaint();
	}

	/**
	 * Toggle XML validation
	 */
	private void optionValidateXml()
	{
		validate = !validate;
	}

	/**
	 * Toggle focus parent behaviour in tree view
	 */
	private void optionFocusParent()
	{
		jFamilyTree.generator.tree.Tree.focusParent = !jFamilyTree.generator.tree.Tree.focusParent;
	}

	// H E L P E R S

	/**
	 * Transform to view
	 */
	public void transformToView(String thisExportUrl, String thisXsltUrlString)
	{
		if (theDocument != null)
		{
			// type of view
			boolean isHtml = thisExportUrl.endsWith("html"); //$NON-NLS-1$
			JTextComponent thisView = isHtml ? makeHtmlView(this) : makeTextView();
			JComponent thisComponent = new JScrollPane(thisView);

			// tab
			theTabbedPane.addTab(Messages.getString("Controller.20"), null, thisComponent, null); //$NON-NLS-1$
			int thisIndex = theTabbedPane.indexOfComponent(thisComponent);
			theTabbedPane.setTabComponentAt(thisIndex, new ButtonTabComponent(theTabbedPane));
			theTabbedPane.setToolTipTextAt(thisIndex, thisXsltUrlString);
			theTabbedPane.setSelectedIndex(thisIndex);

			try
			{
				URL thisXsltUrl = new URL(thisXsltUrlString);
				DomTransformer thisTransformer = new DomTransformer(isHtml, null);
				String thisText = thisTransformer.documentToString(theDocument, thisXsltUrl);
				thisView.setText(thisText);
				thisView.setCaretPosition(0);
				return;
			}
			catch (TransformerConfigurationException thisException)
			{
				String[] theseLines = { thisException.toString(), thisException.getMessage() };
				JOptionPane.showMessageDialog(null, theseLines, Messages.getString("Controller.0"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$
			}
			catch (TransformerException thisException)
			{
				String[] theseLines = { thisException.toString(), thisException.getMessage() };
				JOptionPane.showMessageDialog(null, theseLines, Messages.getString("Controller.0"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$
			}
			catch (IOException thisException)
			{
				String[] theseLines = { thisException.toString(), thisException.getMessage() };
				JOptionPane.showMessageDialog(null, theseLines, Messages.getString("Controller.0"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$
			}
			return;
		}
		String[] theseLines = { Messages.getString("Controller.50") }; //$NON-NLS-1$
		JOptionPane.showMessageDialog(null, theseLines, Messages.getString("Controller.0"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$
	}

	/**
	 * Transform to file
	 */
	public void transformToFile(String thisExportUrl, String thisXsltUrlString, String thisOutputFileUrl)
	{
		if (theDocument != null)
		{
			// type of view
			boolean isHtml = thisExportUrl.endsWith("html"); //$NON-NLS-1$
			try
			{
				File thisOutputFile = getFile(thisOutputFileUrl);
				if (thisOutputFile.exists() && !confirm(new String[] { thisOutputFileUrl, Messages.getString("Controller.40"), Messages.getString("Controller.41") })) //$NON-NLS-1$ //$NON-NLS-2$
					return;
				URL thisXsltUrl = new URL(thisXsltUrlString);
				DomTransformer thisTransformer = new DomTransformer(isHtml, null);
				thisTransformer.documentToFile(theDocument, thisXsltUrl, thisOutputFile);
			}
			catch (TransformerConfigurationException thisException)
			{
				String[] theseLines = { thisException.toString(), thisException.getMessage() };
				JOptionPane.showMessageDialog(null, theseLines, Messages.getString("Controller.0"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$
			}
			catch (TransformerException thisException)
			{
				String[] theseLines = { thisException.toString(), thisException.getMessage() };
				JOptionPane.showMessageDialog(null, theseLines, Messages.getString("Controller.0"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$
			}
			catch (IOException thisException)
			{
				String[] theseLines = { thisException.toString(), thisException.getMessage() };
				JOptionPane.showMessageDialog(null, theseLines, Messages.getString("Controller.0"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$
			}
			return;
		}
		String[] theseLines = { Messages.getString("Controller.50") }; //$NON-NLS-1$
		JOptionPane.showMessageDialog(null, theseLines, Messages.getString("Controller.0"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$
	}

	// D I A L O G S

	/**
	 * Make site
	 */
	private void makeSite()
	{
		theDocument = makeDocument(theModel);
		if (theDocument != null)
		{
			MakeSiteDialog thisDialog = new MakeSiteDialog(theSettings);
			thisDialog.setDocument(theDocument);
			thisDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
			thisDialog.setVisible(true);
		}
	}

	/**
	 * Run site
	 */
	private void runSite()
	{
		RunSiteDialog thisDialog = new RunSiteDialog(theSettings);
		thisDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
		thisDialog.setVisible(true);
	}

	/**
	 * Persist
	 */
	private void settings()
	{
		XSettingsDialog thisDialog = new XSettingsDialog(theSettings);
		thisDialog.setModal(true);
		thisDialog.setVisible(true);
		thisDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
		if (thisDialog.ok)
		{
			jFamilyTree.commons.Persist.saveSettings("jFamilyTree-generator", theSettings); //$NON-NLS-1$
			thePropertyView.setImageRepository(makeImageRepositoryURL());
			updateView();
		}
	}

	/**
	 * Get base
	 */
	private void base()
	{
		XSiteDialog thisDialog = new XSiteDialog(theSettings);
		thisDialog.setModal(true);
		thisDialog.setVisible(true);
		thisDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
	}

	/**
	 * Get Url
	 */
	private void url()
	{
		XUrlDialog thisDialog = new XUrlDialog(theSettings);
		thisDialog.setModal(true);
		thisDialog.setVisible(true);
		thisDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
	}

	/**
	 * Help
	 */
	private void help()
	{
		Help.help(theSettings.getProperty("browser"), theSettings.getProperty("help")); //$NON-NLS-1$ //$NON-NLS-2$
	}

	/**
	 * About
	 */
	private void about()
	{
		new AboutDialog().setVisible(true);
	}

	/**
	 * Confirm
	 * 
	 * @param thisMessage
	 *        message
	 * @return true if confirmed
	 */
	private boolean confirm(String[] thisMessage)
	{
		int thisValue = JOptionPane.showConfirmDialog(null, thisMessage, Messages.getString("Controller.1"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$
		return thisValue == JOptionPane.YES_OPTION;
	}

	/**
	 * Ask
	 * 
	 * @param thisMessage
	 *        message
	 * @return input
	 */
	private String ask(String thisMessage, String thisInitial)
	{
		String[] theseLines = thisMessage.split("\n"); //$NON-NLS-1$
		return JOptionPane.showInputDialog(null, theseLines, thisInitial);
	}

	// S E L E C T

	/**
	 * Select tree node
	 * 
	 * @param thisClassName
	 *        class name of tree node to select
	 */
	private void select(String thisClassName)
	{
		DefaultMutableTreeNode thisNode = theTreeView.search(thisClassName);
		if (thisNode != null)
			theTreeView.select(thisNode);
	}

	// U P D A T E

	/**
	 * Update
	 * 
	 * @param thisMode
	 *        mode
	 */
	public void update(Mode thisMode)
	{
		theMode = thisMode;

		// update view
		updateView();

		// title
		String thisTooltip = ""; //$NON-NLS-1$
		String thisShortTitle = ""; //$NON-NLS-1$
		switch (theMode)
		{
		case OPEN:
			if (theUrl != null)
			{
				thisTooltip = theUrl.toString();
				thisShortTitle = theUrl.getFile();
				int thisIndex = thisShortTitle.lastIndexOf('/');
				if (thisIndex != -1)
					thisShortTitle = thisShortTitle.substring(thisIndex + 1);
				thisShortTitle = " - " + thisShortTitle; //$NON-NLS-1$
			}
			break;
		case NEW:
			thisTooltip = Messages.getString("Controller.80"); //$NON-NLS-1$
			thisShortTitle = Messages.getString("Controller.82"); //$NON-NLS-1$
			break;
		case IMPORT:
			thisTooltip = Messages.getString("Controller.83"); //$NON-NLS-1$
			thisShortTitle = Messages.getString("Controller.84"); //$NON-NLS-1$
			break;
		}
		for (int i = 0; i < 4; i++)
			theTabbedPane.setToolTipTextAt(i, thisTooltip);
		theFrame.setTitle(Messages.getString("Controller.1") + thisShortTitle); //$NON-NLS-1$
	}

	/**
	 * Update current view
	 */
	public void updateView()
	{
		Component thisComponent = theTabbedPane.getSelectedComponent();
		Runnable thisAction = theUpdateMap.get(thisComponent);
		if (thisAction != null)
			thisAction.run();
	}

	/**
	 * Update tree view
	 */
	public void updateTree()
	{
		// model -> tree model
		theTreeModel = theModel == null ? null : new TreeAdapter(theModel);

		// tree view
		theTreeView.set(theTreeModel);
		theTreeView.repaint();

		// property view
		thePropertyView.setIdToNodeMap(theIdToNodeMap);

		// reset dirty flags
		theTreeView.dirty = false;
		thePropertyView.dirty = false;

		// unselect
		picked(null);
	}

	/**
	 * Update widget view
	 */
	public void updateWidget()
	{
		theWidget.init(theModel);
	}

	/**
	 * Update xml text view
	 */
	public void updateText()
	{
		if (theDocument != null)
		{
			try
			{
				String thisText = new DomTransformer(false, "jFamilyTree.dtd").documentToString(theDocument); //$NON-NLS-1$
				theTextView.setText(thisText);
				theTextView.setCaretPosition(0);
			}
			catch (TransformerConfigurationException thisException)
			{
			}
			catch (TransformerException thisException)
			{
			}
			catch (IOException thisException)
			{
			}
		}
	}

	/**
	 * Update Dom view
	 */
	public void updateDom()
	{
		if (theDocument != null)
		{
			theDomTreeView.setDocument(theDocument);
		}
	}

	// V I E W . F A C T O R Y

	/**
	 * Make the Html view
	 * 
	 * @return Html view
	 */
	private HtmlView makeHtmlView(final Controller thisController)
	{
		HtmlView thisHtmlView = new HtmlView();
		thisHtmlView.addHyperlinkListener(thisController);
		return thisHtmlView;
	}

	/**
	 * Make the text view
	 * 
	 * @return text view
	 */
	private TextView makeTextView()
	{
		TextView thisTextView = new TextView();
		thisTextView.setMargin(new Insets(10, 20, 10, 10));
		return thisTextView;
	}

	// U R L . F A C T O R Y

	/**
	 * Make image repository Url
	 * 
	 * @return image repository Url
	 */
	public URL makeImageRepositoryURL()
	{
		String thisImageRepositoryPath = theSettings.getProperty("images"); //$NON-NLS-1$
		if (thisImageRepositoryPath != null)
		{
			File thisFolder = null;
			if (thisImageRepositoryPath.startsWith(Messages.getString("Controller.7"))) //$NON-NLS-1$
				try
				{
					thisFolder = new File(new URI(thisImageRepositoryPath));
				}
				catch (URISyntaxException thisException)
				{
				}
			else
				thisFolder = new File(thisImageRepositoryPath);
			if (thisFolder.exists() && thisFolder.isDirectory())
				try
				{
					return thisFolder.toURI().toURL();
				}
				catch (MalformedURLException thisException)
				{
				}
		}

		URL thisUrl;
		try
		{
			thisUrl = new URL(getBase(), "images"); //$NON-NLS-1$
			return thisUrl;
		}
		catch (MalformedURLException thisException)
		{
		}
		return null;
	}

	/**
	 * Make based Url
	 * 
	 * @param thisSubPath
	 *        if not null this this the extra path to add to the base
	 * @param thisFilename
	 *        filename
	 * @return url
	 */
	private URL makeBasedURL(String thisSubPath, String thisFilename)
	{
		if (thisFilename == null)
			return null;
		try
		{
			return new URL(getBase(), (thisSubPath == null ? "" : (thisSubPath + "/")) + thisFilename); //$NON-NLS-1$ //$NON-NLS-2$
		}
		catch (MalformedURLException e)
		{
			System.err.println(Messages.getString("Controller.210") + thisFilename); //$NON-NLS-1$
			return null;
		}
	}

	/**
	 * Make Url
	 * 
	 * @param thisSource
	 * @return url
	 * @see jFamilyTree.model.IProviderContext#getParameter(java.lang.String)
	 */
	public URL makeURL(String thisSource)
	{
		if (thisSource == null)
			return null;

		// try to consider it well-formed full-fledged url
		try
		{
			return new URL(thisSource);
		}
		catch (MalformedURLException e)
		{
		}

		// try source relative to a base
		URL thisBaseUrl = makeBasedURL(null, thisSource);
		if (thisBaseUrl != null)
			return thisBaseUrl;

		// try to consider it file
		File thisFile = new File(thisSource);
		if (thisFile.exists() && thisFile.canRead())
			try
			{
				return thisFile.toURI().toURL();
			}
			catch (MalformedURLException thisException)
			{
			}

		// fail
		return null;
	}

	// P R O V I D E R . F A C T O R Y

	/**
	 * Make provider
	 * 
	 * @param thisProviderName
	 *        provider name
	 * @return provider
	 */
	private IProvider makeProvider(String thisProviderName)
	{
		try
		{
			Class<?> thisClass = Class.forName(thisProviderName);
			Class<?>[] theseArgsClass = new Class[] {};
			Object[] theseArgs = new Object[] {};

			Constructor<?> thisConstructor = thisClass.getConstructor(theseArgsClass);
			Object thisInstance = thisConstructor.newInstance(theseArgs);
			return (IProvider) thisInstance;
		}
		catch (ClassNotFoundException e)
		{
			System.err.println("Provider factory: " + e.toString()); //$NON-NLS-1$
		}
		catch (NoSuchMethodException e)
		{
			System.err.println("Provider factory: " + e.toString()); //$NON-NLS-1$
		}
		catch (IllegalAccessException e)
		{
			System.err.println("Provider factory: " + e.toString()); //$NON-NLS-1$
		}
		catch (InstantiationException e)
		{
			System.err.println("Provider factory: " + e.toString()); //$NON-NLS-1$
		}
		catch (IllegalArgumentException e)
		{
			System.err.println("Provider factory: " + e.toString()); //$NON-NLS-1$
		}
		catch (InvocationTargetException e)
		{
			System.err.println("Provider factory: " + e.toString()); //$NON-NLS-1$
		}
		return null;
	}

	// C O N T E X T

	/*
	 * (non-Javadoc)
	 * @see jFamilyTree.component.Context#getBase()
	 */
	public URL getBase()
	{
		// base parameter
		String thisBaseSetting = theSettings.getProperty("base"); //$NON-NLS-1$
		String thisBaseParameter = getParameter("base"); //$NON-NLS-1$
		String thisURLString = thisBaseSetting != null ? thisBaseSetting : thisBaseParameter != null ? thisBaseParameter : System.getProperty("user.dir"); //$NON-NLS-1$

		// tail
		if (!thisURLString.endsWith("/")) //$NON-NLS-1$
			thisURLString += "/"; //$NON-NLS-1$

		// make
		try
		{
			return new URL(thisURLString);
		}
		catch (MalformedURLException e)
		{
			// make from folder
			try
			{
				File thisFolder = new File(thisURLString);
				return thisFolder.toURI().toURL();
			}
			catch (MalformedURLException e2)
			{
			}
		}
		return null;
	}

	/*
	 * (non-Javadoc)
	 * @see jFamilyTree.Context#getHtmlDocumentBase()
	 */
	public URL getHtmlDocumentBase()
	{
		return null;
	}

	/*
	 * (non-Javadoc)
	 * @see jFamilyTree.Context#getImagesBase()
	 */
	public URL getImagesBase()
	{
		return makeImageRepositoryURL();
	}

	/*
	 * (non-Javadoc)
	 * @see jFamilyTree.component.Context#getImage(java.net.URL)
	 */
	public Image getImage(URL thisURL)
	{
		return java.awt.Toolkit.getDefaultToolkit().getImage(thisURL);
	}

	/*
	 * (non-Javadoc)
	 * @see jFamilyTree.Context#getParameter(java.lang.String)
	 * @see jFamilyTree.model.IProviderContext#getParameter(java.lang.String)
	 */
	public String getParameter(String thisName)
	{
		// param1=<val> param2=<"val with spaces"> ...
		if (theArgs == null)
			return null;

		for (String thisArg : theArgs)
			if (thisArg.startsWith(thisName + "=")) //$NON-NLS-1$
			{
				String thisValue = thisArg.substring(thisName.length() + 1);
				if (thisValue.startsWith("\"")) //$NON-NLS-1$
				{
					thisValue = thisValue.substring(1);
					if (thisValue.endsWith("\"")) //$NON-NLS-1$
						thisValue = thisValue.substring(0, thisValue.length() - 1);
				}
				return thisValue;
			}
		return null;
	}

	/*
	 * (non-Javadoc)
	 * @see jFamilyTree.component.Context#linkTo(java.lang.String)
	 */
	public boolean linkTo(String thisLinkUrl, String thisUrlTarget)
	{
		System.out.println(Messages.getString("Controller.101") + thisLinkUrl + " , " + thisUrlTarget); //$NON-NLS-1$ //$NON-NLS-2$
		return true;
	}

	/*
	 * (non-Javadoc)
	 * @see jFamilyTree.component.Context#showStatus(java.lang.String)
	 */
	public void showStatus(String thisString)
	{
		System.out.println(Messages.getString("Controller.100") + thisString); //$NON-NLS-1$
	}

	// C O N T E X T

	/*
	 * (non-Javadoc)
	 * @see jFamilyTree.model.IProviderContext#putMessage(java.lang.String)
	 */
	public void putMessage(String thisString)
	{
		System.out.println(thisString);
	}

	/*
	 * (non-Javadoc)
	 * @see jFamilyTree.model.IProviderContext#putProgress(java.lang.String, boolean)
	 */
	public void putProgress(String thisString, boolean thisFail)
	{
		System.out.println(thisString);
	}
}
